/*
 * Created on Mar 8, 2010
 */
package ro.letsdoitromania;

import java.io.IOException;
import java.util.TimerTask;
import java.util.Vector;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDletStateChangeException;
import org.json.me.JSONArray;
import ro.letsdoitromania.forms.*;
import com.mgmaps.app.*;
import com.mgmaps.gps.GPSInternal;
import com.mgmaps.maps.GoogleLayers;
import com.mgmaps.maps.GoogleMap;
import com.mgmaps.maps.OpenStreetMap;
import com.mgmaps.net.DownloadHandler;
import com.mgmaps.tools.GeoTools;
import com.mgmaps.tools.StringTools;
import com.mgmaps.tools.UITools;
import com.mgmaps.utils.*;
import com.nutiteq.BasicMapComponent;
import com.nutiteq.cache.CachingChain;
import com.nutiteq.cache.MemoryCache;
import com.nutiteq.cache.RmsCache;
import com.nutiteq.components.MapPos;
import com.nutiteq.components.Place;
import com.nutiteq.components.WgsPoint;
import com.nutiteq.maps.GeoMap;
import com.nutiteq.net.DefaultDownloadStreamOpener;

public class LDIRCanvas extends APPCanvas implements DownloadHandler {
  
  public static LDIRCanvas cinstance;
  
  protected Image touchImgZoomIn;
  protected Image touchImgZoomOut;
  protected Image touchImgMenu;
  protected Image touchImgLocateMe;
  protected Image touchImgBG;
  
  protected boolean showGPSMessage;
  
  private MapPos lastMP;
  
  private boolean showPhoto;
  private Image photoImage;
  
  private static final Place[] emptyMapPlaces = new Place[0];
  private volatile Place[] mapPlaces = emptyMapPlaces;
  
  protected int welcomeScreenID;
  
  protected Image[][] welcomeIconSets;
  
  protected static final int GPSMESSAGE_TIMEOUT = 20000;
  
  class HideGPSMessage extends TimerTask {
    public void run() {
      showGPSMessage = false;
      invalidate(INV_MESSAGE);
    }
  }

  public LDIRCanvas() {
    super();
    LDIRCanvas.cinstance = (LDIRCanvas) instance;
    welcomeScreenID = 1;
  }
  
  protected void loadOther() {
    // splash screen shown for 2.5 seconds
    splashTimerInterval = 2500;
    
    // TODO load other settings: change key hints, change predefined colors  
  }
  
  protected void loadImages() throws IOException {
    final int minWH = Math.min(oldw, oldh);
    
    if (minWH < 240)
      splash = Image.createImage("/img/splash1.png");
    else if (oldh < 320 && oldw >= 380)
      splash = Image.createImage("/img/splash5.png");
    else if (oldh < 480 || oldw < 360)
      splash = Image.createImage("/img/splash2.png");
    // else if (oldw >= 640 && oldh >= 360)
    //   splash = Image.createImage("/img/splash4.jpg");
    else
      splash = Image.createImage("/img/splash3.png");
    
    if (oldw < 160) {
      final Image titleBig = Image.createImage("/img/title1.png");
      title = titleBig;
      titlePad = UITools.extractImage(titleBig, 16, 7, 0, 16);
    }
    else if (oldw < 240) {
      final Image titleBig = Image.createImage("/img/title2.png");
      title = UITools.extractImage(titleBig, 160, 0, 0, 45);
      titlePad = UITools.extractImage(titleBig, 16, 10, 0, 45);
    }
    else if (oldw < 360) {
      final Image titleBig = Image.createImage("/img/title3.png");
      title = UITools.extractImage(titleBig, 240, 0, 0, 45);
      titlePad = UITools.extractImage(titleBig, 16, 15, 0, 45);
    }
    else {
      final Image titleBig = Image.createImage("/img/title4.png");
      title = UITools.extractImage(titleBig, 360, 0, 0, 60);
      titlePad = UITools.extractImage(titleBig, 24, 15, 0, 60);
    }
    titleHeight = title.getHeight();
    titleWidth = title.getWidth();
    titlePadWidth = titlePad.getWidth();

    keyImage = Image.createImage("/img/keys.png");
    keyIconW = keyImage.getWidth()/NUM_KEYS;
    keyIconH = keyImage.getHeight();    
    
    // welcome
    
    ExtLogger.debug("pointer events: " + pointerEvents);
    
    if (APPSettings.featureMap) {
      boolean touchBig = pointerEvents && (minWH >= 480);
      touchMul = touchBig ? 2 : pointerEvents ? 1 : 0;
      setMapSize(oldw, oldh);
      
      if (pointerEvents) {
        touchZoomWidth = 65;
        touchZoomHeight = 30;
        touchButtonWidth = 38;
        touchBGWidth = 16;
        touchBGHeight = 42;

        final Image touch = Image.createImage(touchBig ? "/img/touchb.png" : "/img/touchs.png");
        touchImgZoomIn = UITools.extractImage(touch, touchZoomWidth*touchMul, 0, 0, touchZoomHeight*touchMul);
        touchImgZoomOut = UITools.extractImage(touch, touchZoomWidth*touchMul, 1, 0, touchZoomHeight*touchMul);
        touchImgLocateMe = UITools.extractImage(touch, touchButtonWidth*touchMul, 4, 0, touchZoomHeight*touchMul);
        touchImgMenu = UITools.extractImage(touch, touchButtonWidth*touchMul, 5, 0, touchZoomHeight*touchMul);
        touchImgBG = UITools.extractImage(touch, touchBGWidth*touchMul, 15, 0, touchBGHeight*touchMul);
      }
    }

    if (Settings.featureMap && Settings.missingTileImage == null)
      try {
        Settings.missingTileImage = Image.createImage("/img/nomap.png");
      }
      catch (Exception ex) {
      }
      
    // welcome
      
    // load image and split into icons
    welcomeIconSets = new Image[2][];

    Image image = Image.createImage(minWH >= 240 ? "/img/welcomeb.png" : "/img/welcomes.png");
    int welcomeNum = welcomeScreenOptions(1).length;
    welcomeIconSets[0] = new Image[welcomeNum];
    for (int i=0; i<welcomeNum; i++)
      welcomeIconSets[0][i] = UITools.extractImage(image, welcomeNum, i);
    welcomeIcons = welcomeIconSets[0];

    image = Image.createImage(minWH >= 240 ? "/img/reportb.png" : "/img/reports.png");
    welcomeNum = welcomeScreenOptions(2).length;
    welcomeIconSets[1] = new Image[welcomeNum];
    for (int i=0; i<welcomeNum; i++)
      welcomeIconSets[1][i] = UITools.extractImage(image, welcomeNum, i);
    
    // place icons
    LDIRPlace.loadIcons();
  }
  
  protected void setUserAgent(DefaultDownloadStreamOpener dso) {
    if (APPSettings.isBlackberry && APPSettings.platformHandler != null && APPSettings.platformHandler.getUserAgent() != null)
      dso.addRequestProperty("User-Agent", APPSettings.platformHandler.getUserAgent());
    else {
      dso.addRequestProperty("User-Agent", "Nokia6600/1.0 (4.09.1) SymbianOS/7.0s Series60/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0");
      dso.addRequestProperty("Profile", "nds.nokia.com/uaprof/N6600r100.xml");
      dso.addRequestProperty("X-WAP-Profile", "nds.nokia.com/uaprof/N6600r100.xml");
    }
  }
  
  protected void setMapSize(int w, int h) {
    mapWidth = w;
    mapHeight = oldh - touchBGHeight*touchMul - titleHeight;
  }
  
  protected void setMapPosition() {
    map.setScreenPosition(0, titleHeight);
  }
  
  protected GeoMap getGeoMap(int mt) {
    switch(mt) {
      case Settings.MAP_OSM:
        return OpenStreetMap.MAP;
      case Settings.MAP_GMM:
        return new GoogleMap(GoogleLayers.MT_MAPMAKER_MAP);
      case Settings.MAP_GMH:
        return new GoogleMap(GoogleLayers.MAP_MAPMAKER_HYB);
      case Settings.MAP_GMAP:
        return new GoogleMap(GoogleLayers.MT_MAP);
      case Settings.MAP_GHYB:
        return new GoogleMap(GoogleLayers.MAP_HYBRID);
      default:
        return null;
    }
  }
  
  protected void setMapAttributes() {
    // panning strategy
    map.setPanningStrategy(new ExtPanning());

    // zoom level indicator
    map.showZoomLevelIndicator(true);
    
    // download display
    map.enableDownloadDisplay();
    
    // cursor
    map.showDefaultCursor();
    
    // touch click
    map.setTouchClickTolerance(BasicMapComponent.FINGER_CLICK_TOLERANCE);
    
    // error listener
    map.setErrorListener(new BBRedetectExtension());
    
    // key handler
    kh = new ExtKeyHandler();
    map.setControlKeysHandler(kh);

    // map listener
    if (Settings.featureDynamicPlaces) {
      ml = new DelayedMapListener(this);
      map.setMapListener(ml);
    }
    else
      map.setMapListener(this);
  }
  
  protected void loadMapCache() {
    // map.setNetworkCache(new RmsCache("ML_NETWORK_CACHE", 128 * 1024, 8));
    final MemoryCache memoryCache = new MemoryCache(384 * 1024);
    final RmsCache rmsCache = new RmsCache("ML_NETWORK_CACHE", 128 * 1024, 8);
    map.setNetworkCache(new CachingChain(new com.nutiteq.cache.Cache[] { memoryCache, rmsCache }));
  }
  
  /**
   * Set this canvas as the current displayable.
   */
  protected void setCanvas() {
    Display.getDisplay(APPMidlet.instance).setCurrent(this);
  }
  
  protected void initializeStart() {
    // TODO stuff to do at the beginning of initialize()
    // for instance, show some screen
  }
  
  protected void initializeCommands() {
    Commands.initialize();
  }
  
  protected void initializeDownloader() {
    Settings.initializeDownloader(this);
  }
  
  protected void initializeOther() {
    // other stuff for initialize()
  }
  
  protected void addCommands() {
    // add commands for the map screen
    try {
      removeCommand(Commands.instance.findmeCommand);
    }
    catch (Exception ex) {
    }
    if (!inWelcome && !showPhoto)
      addCommand(Commands.instance.findmeCommand);

    try {
      removeCommand(Commands.cinstance.mapTypeCommand);
    }
    catch (Exception ex) {
    }
    if (!inWelcome && !showPhoto)
      addCommand(Commands.cinstance.mapTypeCommand);
    
    try {
      removeCommand(Commands.instance.mainBackCommand);
    }
    catch(Exception ex) {
    }
    if (!inWelcome && !showPhoto)
      addCommand(Commands.instance.mainBackCommand);
    
    try {
      removeCommand(Commands.instance.backCommand);
    }
    catch (Exception ex) {
    }
    if (showPhoto)
      addCommand(Commands.instance.backCommand);
  }

  protected boolean firstTimeInitialization() {
    return true;
  }

  /**
   * Called when GPS position is updated
   */
  public void notifyGPSUpdate(boolean force) {
    final GPSPlace pl = gps.getPlace();
    if (pl == null)
      return;
    
    final WgsPoint wgs = pl.getWgs();
    
    // TODO add code
    
    if (Settings.gpsEnabled && gps.getReceiver() != null) {

      if (force)
        gps.enableSync();
    
      if (Settings.gpsSyncEnabled && wgs != null)
        map.moveMap(wgs);
      
      mapMoved(); // to enqueue places and repaint
      invalidate(INV_GPS);
    }
  }

  /**
   * Action when a button overlay is selected.
   * @param type overlay id
   * @param selected selected item
   * @see com.mgmaps.app.APPCanvas#buttonOverlayAction(int, int)
   */
  protected void buttonOverlayAction(int type, int selected) {
    // TODO button overlays
  }

  /**
   * Check if we should process place clicks.
   * @return true by default
   * @see com.mgmaps.app.APPCanvas#getClickSourcePlaceClicked()
   */
  protected boolean getClickSourcePlaceClicked() {
    // additional condition (AND) if we don't want to always process place clicked
    return true;
  }

  protected String getMapLicenseAppName() {
    return "Let's Do It Romania";
  }

  protected String getMapLicenseKey() {
    return "b83aac23b9528732c23cc7352950e8804c00ae163b9325.47836400";
  }

  protected String getMapLicenseVendor() {
    return "Cristian Streng";
  }

  protected void initializeMapEnd() {
    // TODO things to add at map initialization end
  }

  protected void initializeMapStart() {
    // things to add at map initialization start
    messageShownTrue();
  }
  
  private void messageShownTrue() {
    showGPSMessage = true;
    APPSettings.timer.schedule(new HideGPSMessage(), GPSMESSAGE_TIMEOUT);
  }

  protected void initializeOther1() {
    // TODO initialization after creating downloader
  }

  protected void initializeOther2() {
    // TODO initialization at the end
  }
  
  protected WelcomePaintArgs createWelcomePaintArgs() {
    return new WelcomePaintArgsImpl();
  }

  protected boolean keyPressedCustom(int keyCode, int action) {
    // find me shortcut
    if (keyCode == KEY_NUM6 || keyCode == 'f') {
      commandAction(Commands.instance.findmeCommand, this);
      return true;
    }
    
    else if (keyCode == KEY_NUM8 || keyCode == 'l') {
      commandAction(LogForm.SHOW_LOG, this);
      return true;
    }
    
    else if (keyCode == KEY_NUM9 || keyCode == 'm') {
      commandAction(Commands.cinstance.mapTypeCommand, this);
      return true;
    }
    
    // custom key handling
    return false;
  }

  protected boolean keyReleasedCustom(int keyCode, int action) {
    // custom key handling
    if (keyCode == KEY_NUM6 || keyCode == 'f' || keyCode == KEY_NUM8 ||
        keyCode == 'l' || keyCode == KEY_NUM9 || keyCode == 'm')
      return true;
    
    return false;
  }

  protected boolean keyRepeatedCustom(int keyCode, int action) {
    // custom key handling
    if (keyCode == KEY_NUM6 || keyCode == 'f' || keyCode == KEY_NUM8 ||
        keyCode == 'l' || keyCode == KEY_NUM9 || keyCode == 'm')
      return true;
    
    return false;
  }

  protected void pointerDraggedCustom(int x, int y) {
    // custom pointer handling
  }

  protected void pointerPressedCustom(int x, int y) {
    // custom pointer handling
  }

  protected void pointerReleasedCustom(int x, int y) {
    // custom pointer handling
  }

  protected void pointerReleasedCustomMap(int x, int y) {
    // TODO custom pointer released handling when released on the map
  }

  protected void placeClickedAction() {
    int type = ((LDIRPlace) selectedPlace).getType();
    if (type == LDIRPlace.PT_TRASHPOINT)
      PlaceDetails.getInstance().show((LDIRPlace) selectedPlace);
    // TODO implement cluster details
  }

  protected boolean pointerDraggedDisableGPSSync() {
    // TODO additional condition (AND) to disable GPS sync
    return true;
  }

  protected boolean pointerReleasedKeyRelease() {
    // TODO additional condition (AND) whether to send keyRelease for click source
    return true;
  }

  protected void welcomeScreenAction(int selected) {
    try {
      switch(welcomeScreenID) {
        case 1:
          switch (selected) {
            case 0:
              actionNearby();
              break;
            case 1:
              actionReportScreen();
              break;
            case 2:
              actionSettings();
              break;
            case 3:
              actionAbout();
              break;
            case 4:
              actionShare();
              break;
            case 5:
              actionExit();
              break;
          }
          break;
          
        case 2:
          switch (selected) {
            case 0:
              actionAdd();
              break;
            case 1:
              actionView();
              break;
            case 2:
              actionSend();
              break;
            case 3:
              actionBackToWelcome();
              break;
          }
          break;
      }
    }
    catch (Exception ex) {
      ExtLogger.handle(ex, false, "Error");
    }
  }
  
  private void actionChangeScreen(int i) {
    welcomeScreenID = i;
    welcomeIcons = welcomeIconSets[i-1];
    createWelcomeScreen();
    invalidate(INV_WELCOME);
  }
  
  private void actionNearby() {
    if (inWelcome) {
      inWelcome = false;
      if (Settings.isNokiaTouch)
        setFullScreenMode(false);
    }
    initializeMap();
  }
  
  private void actionReportScreen() {
    actionChangeScreen(2);
  }
  
  private void actionAbout() {
    AboutForm.getInstance().show();
  }
  
  private void actionSettings() {
    SettingsForm.getInstance().show();
  }
  
  private void actionShare() {
    ShareForm.getInstance().show();
  }
  
  public void actionExit() {
    try {
      LDIRMidlet.instance.destroyApp(true);
    }
    catch (MIDletStateChangeException ignore) {
    }
  }
  
  private void actionAdd() {
    AddEditForm.getInstance().showAdd();
  }
  
  private void actionView() {
    if (Settings.points.size() == 0)
      showAlert(LS.get("ldir.viewlist.title"), LS.get("ldir.viewlist.nopoints"));
    else
      ViewList.getInstance().show();
  }
  
  private void actionSend() {
    if (Settings.points.size() == 0)
      showAlert(LS.get("ldir.upload.title"), LS.get("ldir.upload.nopoints"));
    else if (Settings.username.length() == 0 || Settings.password.length() == 0)
      LoginForm.getInstance().show();
    else
      startUpload();
  }
  
  private void actionBackToWelcome() {
    if (!inWelcome) {
      inWelcome = true;
      if (Settings.isNokiaTouch)
        setFullScreenMode(true);
    }
    actionChangeScreen(1);
  }
  
  public void startUpload() {
    PendingForm.instance.show(this, LS.get("ldir.upload.title"), LS.get("ldir.upload.pending"));
    // auth request if salt is not set
    if (Settings.usersalt.length() == 0)
      Settings.dl.enqueue(new AuthSaltRequest());
    else
      Settings.dl.enqueue(new TrashPointRequest());
  }

  /**
   * Set new language items.
   */
  public void loadNewLanguage() {
    // reload welcome screen texts
    // welcomeScreen.setOptions(welcomeScreenOptions());
    createWelcomeScreen();
    
    // reload commands
    Commands.initialize();
  }

  protected String[] welcomeScreenOptions() {
    return welcomeScreenOptions(welcomeScreenID);
  }

  protected String[] welcomeScreenOptions(int i) {
    switch(i) {
      case 1:
        return new String[] {
            LS.get("ldir.start.nearby"),
            LS.get("ldir.start.report"),
            LS.get("ldir.start.settings"),
            LS.get("ldir.start.about"),
            LS.get("ldir.start.share"),
            LS.get("ldir.start.exit")
        };
      case 2:
        return new String[] {
            LS.get("ldir.start.add"),
            LS.get("ldir.start.view"),
            LS.get("ldir.start.send"),
            LS.get("ldir.start.back")
        };
      default:
        return null;
    }
  }

  public void notifyDownloaded(String url, byte[][] data) {
    // TODO Downloader implementation
    try {
      final String response = StringTools.byteArrayToString(data[0]);
      // ExtLogger.debug("Downloaded " + response);
      
      if (url.startsWith(Settings.DOWNLOAD_URL)) {
        // downloaded places
        final LDIRPlace[] newPlaces = (LDIRPlace[]) parsePlaces(response);
        map.removePlaces(mapPlaces);
        mapPlaces = extractMapPlaces(newPlaces);
        places = newPlaces;
        map.addPlaces(mapPlaces);
        invalidate(INV_PLACE);
      }
      
      else if (url.startsWith(Settings.PHOTO_DL_URL)) {
        final Image img = Image.createImage(data[0], 0, data[0].length);
        photoImage = img;
        invalidate(INV_SHOW);
      }
    }
    catch (Exception ex) {
      ExtLogger.handle(ex, false, "Exception at notifyDownloaded");
    }
  }

  public void notifyError(String url, String message) {
    handleError(url, "Network error");
  }
  
  private void handleError(final String url, final String message) {
    ExtLogger.error("Error downloading " + url + ", message: " + message);
  }
  
  private Place[] extractMapPlaces(final LDIRPlace[] newPlaces) {
    final int num = newPlaces.length;
    final Place[] pl = new Place[num];
    for (int i=0; i<num; i++)
      pl[i] = newPlaces[i].getPlace();
    return pl;
  }
  
  private APPPlace[] parsePlaces(final String response) {
    try {
      JSONArray json = new JSONArray(response);
      Vector placeVector = new Vector();
      int num = json.length();
      for (int i=0; i<num; i++) {
        try {
          final LDIRPlace pl = new LDIRPlace(json.getJSONObject(i));
          placeVector.addElement(pl);
        }
        catch (Exception ex) {
          ExtLogger.handle(ex, false, "Exception at parsing places");
          break;
        }
      }
      
      final LDIRPlace[] newPlaces = new LDIRPlace[placeVector.size()];
      placeVector.copyInto(newPlaces);
      placeVector.removeAllElements();
      placeVector = null;
      
      return newPlaces;
    }
    catch (Exception ex2) {
      ExtLogger.handle(ex2, false, "Exception at parsing places");
      return emptyPlaces;
    }
  }

  public void commandAction(Command cmd, Displayable disp) {
    if (cmd == Commands.instance.exitCommand)
      actionExit();
    
    else if (cmd == Commands.instance.mainBackCommand) {
      if (!inWelcome) {
        inWelcome = true;
        if (Settings.isNokiaTouch)
          setFullScreenMode(true);
      }
      addCommands();
      invalidate(INV_WELCOME);
    }
    
    else if (cmd == Commands.instance.findmeCommand) {
      gps.enableSync();
      messageShownTrue();
    }
    
    else if (cmd == Commands.cinstance.mapTypeCommand)
      MapTypeForm.getInstance().show();
    
    else if (cmd == LogForm.SHOW_LOG)
      Display.getDisplay(LDIRMidlet.instance).setCurrent(
          new LogForm(map, LDIRMidlet.instance, this));
    
    // should  be available only in showPhoto
    else if (cmd == Commands.instance.backCommand)
      photoHidden();
    
    // TODO command actions
  }
  
  /**
   * Paint method.
   * @param g Graphics object
   * @see com.mgmaps.app.APPCanvas#paint(javax.microedition.lcdui.Graphics)
   */
  protected void paint(Graphics g) {
    super.paint(g);
  }
  
  protected void paintWelcome(Graphics g) {
    if (showPhoto) {
      paintPhoto(g);
      return;
    }
    
    super.paintWelcome(g);
  }
  
  /**
   * Paint the controls over the map.
   * @param g Graphics object
   */
  protected void paintControls(Graphics g) {
    paintTitleBar(g);
    
    if (pointerEvents) {
      // paint the touch screen controls
      for (int i=0; i<mapWidth; i+=touchBGWidth*touchMul)
        g.drawImage(touchImgBG, i, mapHeight+titleHeight, Graphics.TOP|Graphics.LEFT);
      final int h = mapHeight+touchBGHeight*touchMul/2-touchZoomHeight*touchMul/2+titleHeight;
      g.drawImage(touchImgZoomIn, mapWidth/2-touchZoomWidth*touchMul, h, Graphics.TOP|Graphics.LEFT);
      g.drawImage(touchImgZoomOut, mapWidth/2, h, Graphics.TOP|Graphics.LEFT);
      g.drawImage(touchImgMenu, 9, h, Graphics.TOP|Graphics.LEFT);
      g.drawImage(touchImgLocateMe, mapWidth-9-touchButtonWidth*touchMul, h, Graphics.TOP|Graphics.LEFT);
    }
  }
  /**
   * Paint the map.
   * @param g Graphics object
   */
  protected void paintMap(Graphics g) {
    if (showPhoto) {
      paintPhoto(g);
      return;
    }
    
    // paint map to buffer if needed
    if (isInvalid(INV_MAP | INV_PLACE | INV_SHOW)) {
    // if (isInvalid(INV_ANY)) {
      // if (ra != null)
      //   ra.updatedCachedAngle();
      dblbufg.setClip(0, titleHeight, mapWidth, mapHeight);
      map.paint(dblbufg);
    }
    clearInvalid();
    
    // paint map buffer
    g.setClip(0, titleHeight, mapWidth, mapHeight);
    g.drawImage(dblbuf, 0, 0, Graphics.TOP | Graphics.LEFT);

    // title, touch arrows, zoom buttons
    g.setClip(0, 0, mapWidth, titleHeight+mapHeight+touchBGHeight*touchMul);
    paintControls(g);

    // key hints
    g.setClip(0, titleHeight, mapWidth, mapHeight);
    g.setFont(font);
    paintKeyHints(g);
    
    if (APPSettings.featureGPS) {
      try {
        if (!APPSettings.gpsEnabled)
          throw new Exception();
        
        final GPSPlace gpsPlace = gps.getPlace();
        if (gpsPlace == null || gpsPlace.getWgs() == null)
          throw new Exception();
        
        // paint GPS place
        // LOW remove this line?
        gpsPlace.updateMapPos();
        gpsPlace.paint(g, mapWidth, mapHeight);
        // gpsON = true; // <-- manual location
      }
      catch (Exception ex) {
        if (showGPSMessage) {
          // no GPS fix, show message
          if (!Settings.gpsEnabled)
            showMessage(g, LS.get("gps.status.disabled"));
          else if (gps.getReceiver() == null)
            showMessage(g, LS.get("gps.status.notconf"));
          else if ((gps.getReceiver() instanceof GPSInternal) && !((GPSInternal) gps.getReceiver()).isConnected())
            showMessage(g, LS.get("gps.status.notavail"));
          else
            showMessage(g, LS.get("gps.status.nofix"));
        }
      }
    } // if (featureGPS)
    
    g.setClip(0, 0, mapWidth, titleHeight+mapHeight+touchBGHeight*touchMul);
    if (btnOverlay != null)
      btnOverlay.paint(g);
    
    // TODO other map painting
  }
  
  private void paintPhoto(Graphics g) {
    int dw = mapWidth;
    int dh = titleHeight+mapHeight+touchBGHeight*touchMul;
    g.setClip(0, 0, dw, dh);
    g.setColor(colorBlack);
    g.fillRect(0, 0, dw, dh);
    if (photoImage != null) {
      int w = photoImage.getWidth();
      int h = photoImage.getHeight();
      g.drawImage(photoImage, (dw-w)>>1, (dh-h)>>1, Graphics.TOP|Graphics.LEFT);
    }
    else {
      String msg = LS.get("ldir.photo.loading");
      int w = font.stringWidth(msg);
      int h = fontHeight;
      g.setColor(colorYellow);
      g.drawString(msg, (dw-w)>>1, (dh-h)>>1, Graphics.TOP|Graphics.LEFT);
    }
  }

  protected void downloadPlaces(boolean force) {
    // TODO add option to move map to Romania
    // TODO percentage sum must be 100
    // TODO show GPS info
    // TODO showphoto from main screen (report view)
    /*
    final WgsBoundingBox bbox = map.getBoundingBox();
    final WgsPoint pp1 = bbox.getWgsMin();
    final WgsPoint pp2 = bbox.getWgsMax();
    ExtLogger.debug("bbox=" + pp1.getLon() + "," + pp1.getLat() + "," + pp2.getLon() + "," + pp2.getLat());
    */
    
    final WgsPoint p = map.getCenterPoint();
    final MapPos mp = map.getMapPosition(p);
    
    if (!force && lastMP != null) {
      if (lastMP.getZoom() == mp.getZoom() &&
          Math.abs(lastMP.getX()-mp.getX()) < mapWidth>>1 &&
          Math.abs(lastMP.getY()-mp.getY()) < mapHeight>>1)
        return;
    }
    lastMP = mp;
    
    final MapPos mp1 = new MapPos(mp.getX()-mapWidth, mp.getY()+mapHeight, mp.getZoom());
    final MapPos mp2 = new MapPos(mp.getX()+mapWidth, mp.getY()-mapHeight, mp.getZoom());
    final WgsPoint p1 = GeoTools.fromPoint(map.getMap().mapPosToWgs(mp1));
    final WgsPoint p2 = GeoTools.fromPoint(map.getMap().mapPosToWgs(mp2));
    String url = Settings.DOWNLOAD_URL + "?bbox=" + p1.getLon() + "," + p1.getLat();
    url += "," + p2.getLon() + "," + p2.getLat() + "&w=" + (mapWidth<<1) + "&h=" + (mapHeight<<1);
    url += "&z=" + map.getZoom(); 
    
    Settings.dl.enqueue(url);
  }

  /**
   * Rebuild the canvas to show photos.
   */
  public void photoShown() {
    if (!showPhoto) {
      showPhoto = true;
      if (Settings.isNokiaTouch)
        setFullScreenMode(inWelcome);
      addCommands();
      invalidate(INV_SHOW);
      Display.getDisplay(LDIRMidlet.instance).setCurrent(this);
    }
  }
  
  /**
   * Rebuild the canvas to show the map and not photos.
   */
  public void photoHidden() {
    if (showPhoto) {
      photoImage = null;
      showPhoto = false;
      if (Settings.isNokiaTouch)
        setFullScreenMode(inWelcome);
      addCommands();
      invalidate(INV_SHOW);
      Display.getDisplay(LDIRMidlet.instance).setCurrent(PlaceDetails.getInstance().getForm());
    }
  }
}
